package debtbundler.presentation.balancesheeteditor;

import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JCheckBox;
import javax.swing.JFormattedTextField;
import javax.swing.JLabel;

import debtbundler.domain.bll.PaymentSharePair;
import debtbundler.domain.bll.Utils;
import debtbundler.domain.entity.Payment;
import debtbundler.domain.entity.Person;
import debtbundler.domain.entity.Share;
import debtbundler.domain.entity.SharedCost;
import debtbundler.domain.entity.impl.EntityManager;
import debtbundler.presentation.VAlignment;
import debtbundler.presentation.basecomponents.SelectableDomainEditorImpl;
import debtbundler.presentation.utils.IconConstants;
import debtbundler.presentation.utils.UIUtils;

/**
 * Controller and GUI for editing the payments and shares of a <code>Person</code> 
 * for a <code>SharedCost</code>. Max one existing payment and one share are allowed 
 * between the person and the shared cost.
 * 
 * @author Anssi
 *
 */
public class TransactionEditor extends SelectableDomainEditorImpl<PaymentSharePair> implements ItemListener, ActionListener {
	private static final long serialVersionUID = 1L;
	private static Logger logger = Logger.getLogger(TransactionEditor.class.getName());
		
	protected Person person;
	protected SharedCost sharedCost;
	protected Payment payment;
	protected Share share;
	
	protected VAlignment vAlignment;
	protected SharingMethod sharingMethod = SharingMethod.EQUAL;
	protected JLabel participationLabel;
	protected JCheckBox participationButton;
	protected JLabel paymentLabel;
	protected JFormattedTextField paymentField;
	protected JLabel shareLabel;
	protected JFormattedTextField shareField;
	

	
	/***
	 * 
	 * @param person
	 * @param sharedCost
	 * @throws IllegalArgumentException 
	 * 	if there are existing flows between person and sharedCost
	 */
	protected TransactionEditor(Person person, SharedCost sharedCost) throws IllegalArgumentException {
		this(person, sharedCost, VAlignment.CENTER);
	}

	/***
	 * 
	 * @param person
	 * @param sharedCost
	 * @throws IllegalArgumentException 
	 * 	if there are existing flows between person and sharedCost
	 */
	protected TransactionEditor(Person person, SharedCost sharedCost, VAlignment vAlignment) throws IllegalArgumentException {
		super();
		
		if (person == null || sharedCost == null) {
			throw new IllegalArgumentException("Null person or person");
		}
		this.person = person;
		this.sharedCost = sharedCost;
		this.vAlignment = vAlignment;
		
		this.setLayout( new BoxLayout(this, BoxLayout.PAGE_AXIS) );
		createGUI();
		refreshDomainFields();
	}	

	/**
	 * Recreates the GUI components and does layout.
	 */
	private void createGUI() {	
		// Components
		this.removeAll();
		
		paymentLabel = new JLabel(UIUtils.getImageIcon(IconConstants.PAYMENT_16));
		paymentField = new JFormattedTextField(NumberFormat.getCurrencyInstance());
		paymentField.setMaximumSize( new Dimension(paymentField.getMaximumSize().width, paymentField.getPreferredSize().height) );
		paymentField.addActionListener(this);
		paymentLabel.setLabelFor(paymentField);
		
		shareLabel = new JLabel(UIUtils.getImageIcon(IconConstants.SHARE_16));
		if (sharingMethod == SharingMethod.VALUES) {
			shareField = new JFormattedTextField(NumberFormat.getCurrencyInstance());
		} else if (sharingMethod == SharingMethod.PERCENTAGES) {
			shareField = new JFormattedTextField(NumberFormat.getPercentInstance());
		} else { // Equal sharing
			shareField = new JFormattedTextField(NumberFormat.getCurrencyInstance());
			shareField.setEditable(false);
		}
		shareField.setMaximumSize( new Dimension(shareField.getMaximumSize().width, shareField.getPreferredSize().height) );
		shareField.addActionListener(this);
		shareLabel.setLabelFor(shareField);
		
		participationLabel = new JLabel(UIUtils.getEmpty16x16Icon());
		participationButton = new JCheckBox("Osallistuu");
		participationButton.setSelected(true);
		participationButton.addItemListener(this);
		participationButton.setBorder(null);
		
		// Layout
		if (vAlignment == VAlignment.BOTTOM || vAlignment == VAlignment.CENTER) {
			this.add(Box.createVerticalGlue());
		}
		
		Box paymentBox = Box.createHorizontalBox();
		paymentBox.add(paymentLabel);
		paymentBox.add(Box.createRigidArea(new Dimension(6, 0)));
		paymentBox.add(paymentField);
		paymentBox.setBorder(BorderFactory.createEmptyBorder(6, 6, 0, 6));
		this.add(paymentBox);
		
		this.add(Box.createRigidArea(new Dimension(0, 6)));
		
		Box shareBox = Box.createHorizontalBox();
		shareBox.add(shareLabel);
		shareBox.add(Box.createRigidArea(new Dimension(6, 0)));
		shareBox.add(shareField);
		shareBox.setBorder(BorderFactory.createEmptyBorder(0, 6, 0, 6));
		this.add(shareBox);
		
		this.add(Box.createRigidArea(new Dimension(0, 6)));
		
		Box participationBox = Box.createHorizontalBox();
		participationBox.add(participationLabel);
		participationBox.add(Box.createRigidArea(new Dimension(6, 0)));
		participationBox.add(participationButton);
		participationBox.add(Box.createHorizontalGlue());
		participationBox.setBorder(BorderFactory.createEmptyBorder(0, 6, 6, 6));
		this.add(participationBox);
		
		if (vAlignment == VAlignment.TOP || vAlignment == VAlignment.CENTER) {
			this.add(Box.createVerticalGlue());
		}
		
		this.validate();
	}	
	
	/**
	 * Gets the payment and share to edit or creates them if necessary.
	 */
	private void initModel() {
		
		ArrayList<Payment> payments = Utils.findTransactionsOfPool(person.getPayments(), sharedCost);
		ArrayList<Share> shares =  Utils.findTransactionsOfPool(person.getShares(), sharedCost);
		if (payments.size() > 1 || shares.size() > 1) {
			throw new IllegalArgumentException("More than one payment or share not allowed");
		} 
		
		if (payments.size() == 0) {
			payment = EntityManager.createPayment(person, sharedCost);
		} else {
			payment = payments.iterator().next();
		}
		
		if (shares.size() == 0) {
			share = EntityManager.createShare(person, sharedCost);
		} else {
			share = shares.iterator().next();
		}
	}
	
	
	/**
	 * Sets the vertical alignment of fields in this component.
	 * 
	 * @param vAlignment	the vertical alignment
	 */
	public void setVerticalAlignment(VAlignment vAlignment) {
		this.vAlignment = vAlignment != null ? vAlignment : VAlignment.CENTER;
		createGUI();
		refreshDomainFields();
	}

	@Override
	public void itemStateChanged(ItemEvent e) {
		logger.log(Level.FINER, "Caught event: {0}", e);
		
		if (e.getItemSelectable() == this.participationButton) {
			setParticipation();
		}
		
	}

	/**
	 * 
	 */
	private void setParticipation() {
		if (this.participationButton.isSelected()) {
			this.person.addShare(share);
			this.person.addPayment(payment);
			this.shareField.setEnabled(true);
			this.paymentField.setEnabled(true);
			refreshDomainFields();
			fireDomainValueModified();
		} else {
			this.person.removeTransaction(share);
			this.person.removeTransaction(payment);
			this.shareField.setValue(0);
			this.shareField.setEnabled(false);
			this.paymentField.setValue(0);
			this.paymentField.setEnabled(false);
			fireDomainValueModified();
		}
	}
	
	@Override
	public void actionPerformed(ActionEvent e) {
		logger.log(Level.FINER, "Caught event {0}", e);
		
		if (e.getSource() == paymentField) {
			setPaymentValue();
			
		} else if (e.getSource() == shareField) {
			setShareValue();
			
		}
		
		refreshDomainFields();
	}

	/**
	 * 
	 */
	private void setShareValue() {
		try {
			shareField.commitEdit();
			double fieldValue = Double.parseDouble(shareField.getValue().toString());
			double newValue = 0; 
			if (sharingMethod == SharingMethod.PERCENTAGES) {
				newValue = fieldValue * sharedCost.getValue();
			} else {
				newValue = fieldValue;
			}
			
			if (newValue != share.getValue() && newValue >= 0) {
				share.setValue(newValue);
				fireDomainValueModified();
			}
		} catch (ParseException e) {
			// keeping old value
		}
		
	}

	/**
	 * 
	 */
	private void setPaymentValue() {
		try {
			paymentField.commitEdit();
			Double newValue = Double.parseDouble(paymentField.getValue().toString());
			if (newValue != payment.getValue() && newValue >= 0) {
				payment.setValue(newValue);
				fireDomainValueModified();
			}
		} catch (ParseException e) {
			// keeping old value
		}
		
	}

	@Override
	public void refreshDomainFields() {
		initModel(); // balancing may have created new flows, we must replace references here with them
		
		paymentField.setValue( payment.getValue() );
		if (sharingMethod == SharingMethod.PERCENTAGES) {
			double itemCost = sharedCost.getValue();
			if (itemCost > 0) {
				shareField.setValue( Math.abs( share.getValue()/itemCost ) );
			} else {
				shareField.setValue(0);
			}
		} else {
			shareField.setValue( share.getValue() );
		}
		
		logger.log(Level.FINER, "Refreshed values of model fields for flows between person {0} and sharedCost {1}", new Object[]{this.person, this.sharedCost} );
	}

	public SharingMethod getSharingMethod() {
		return sharingMethod;
	}

	public void setSharingMethod(SharingMethod sharingMethod) {
		this.sharingMethod = sharingMethod;
		createGUI();
		refreshDomainFields();
	}

	@Override
	public PaymentSharePair getDomainComponent() {
		return new PaymentSharePair(this.payment, this.share);
	}

	@Override
	public void focusLost(FocusEvent e) {
		logger.log(Level.FINER, "Focust lost by {0}", e.getComponent());
		
		// Must commit value 
		if (e.getComponent() == paymentField) {
			setPaymentValue();
		} else if (e.getComponent() == shareField) {
			setShareValue();
		}
		
		super.focusLost(e);
	}
	
	/**
	 * If <code>enabled == false</code> sets the input fields in this editor 
	 * as read-only. Otherwise sets them editable. 
	 */
	@Override
	public void setEnabled(boolean enabled) {
		super.setEnabled(enabled);
		paymentField.setEditable(enabled);
		shareField.setEditable(enabled && sharingMethod != SharingMethod.EQUAL);
		participationButton.setEnabled(enabled);
	}


	
}
